package com.pclong.doudizhu.server.demo1;
import java.util.*;

/**
 * Description:
 * </br>Copyright (C), 2005-2009, KK
 * </br>This program is protected by copyright laws.
 * </br>Program name: DouDiZhu
 * </br>Date: 12/10/09
 * @author kk
 * @version 1.0
 */

class DDZHandCardsType {
	enum DDZTypes
	{
		BOMB, FOUR, TRIPLE, DFLUSH, FLUSH, DOUBLE, SINGLE, NONE;
	}
	public final DDZTypes type;
	public final int length;
	public final int headValue;
	public final int extra; // special for triple. indicate the number of triples
	
	public DDZHandCardsType()
	{
		this.type = DDZTypes.NONE;
		this.length = 0;
		this.headValue = 0;
		this.extra = 0;
	}
	public DDZHandCardsType(DDZTypes type, int length, int headValue)
	{
		this.type = type;
		this.length = length;
		this.headValue = headValue;
		this.extra = 0;
	}
	public DDZHandCardsType(DDZTypes type, int length, int headValue, int extra)
	{
		this.type = type;
		this.length = length;
		this.headValue = headValue;
		this.extra = extra;
	}
}

// one hand of cards
class DDZHandCards {
	private DDZCards cards;
	private DDZHandCardsType cardsType;
	
	public DDZHandCards()
	{
		this.cards = new DDZCards();
		this.cardsType = new DDZHandCardsType();
	}
	public DDZHandCards(DDZCards inputCards)
	{
		this.cardsType = Rules.check(inputCards);
		if(this.cardsType.type == DDZHandCardsType.DDZTypes.NONE)
			this.cards = new DDZCards();
		else
			this.cards = inputCards;
	}
	
	public DDZHandCardsType getCardsType()
	{
		return this.cardsType;
	}
	public DDZCards getDDZCards()
	{
		return this.cards;
	}
	
	public boolean compareTo(DDZHandCards h)
	{
		if(Rules.compareBetween(this, h)>0)
			return true;
		else
			return false;
	}
	
	public void print()
	{
		cards.print();
	}
}

public class Rules {
	public static DDZHandCardsType check(DDZCards c)
	{
		// determine if the input cards form legally one hand of cards or not
		// if not, cardsType is NONE
		if(c.getLength() == 0)
			return new DDZHandCardsType();
		// 1. sort the cards first
		c.sort();
		// 2. start parse the cards
		int s=0, d=0, t=0, f=0;
		int lastValue=0;
		for(int i=0; i<c.getLength(); i++)
		{
			if(c.getCardAt(i).value != lastValue) // not shown before
				s++;
			else // same value with last card
			{
				if(i == 1)
				{
					s--;
					d++;
				}
				else if(i == 2)
					if(c.getCardAt(i).value != c.getCardAt(i-2).value)
					{
						s--;
						d++;
					}
					else
					{
						d--;
						t++;
					}
				else
				{
					if(c.getCardAt(i).value != c.getCardAt(i-2).value)
					{
						s--;
						d++;
					}
					else
					{
						if(c.getCardAt(i).value != c.getCardAt(i-3).value)
						{
							d--;
							t++;
						}
						else
						{
							t--;
							f++;
						}
					}
				}	
			}
			lastValue = c.getCardAt(i).value;
		}
		
		// 3. try to match the type
		// single
		if(s==1 && d==0 && t==0 && f==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.SINGLE, 1, c.getCardAt(0).value);
		// double Joker
		if(s==2 && d==0 && t==0 && f==0 && c.getCardAt(0).getValueEnum().getName().compareTo("Joker")==0 
				&& c.getCardAt(1).getValueEnum().getName().compareTo("Joker")==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.BOMB, 2, c.getCardAt(0).value);
		// double
		if(s==0 && d==1 && t==0 && f==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.DOUBLE, 2, c.getCardAt(0).value);
		// triple with none
		if(s==0 && d==0 && t==1 && f==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, 3, c.getCardAt(0).value, 1);
		// triple with single
		if(s==1 && d==0 && t==1 && f==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, 4, c.getCardAt(1).value, 1);
		// triple with double, the triple's value must be in the middle
		if(s==0 && d==1 && t==1 && f==0)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, 5, c.getCardAt(2).value, 1);
		// bomb (four)
		if(s==0 && d==0 && t==0 && f==1)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.BOMB, 4, c.getCardAt(0).value);
		// four with 2 single, the four's value must be in the middle
		if(s==2 && d==0 && t==0 && f==1)
			return new DDZHandCardsType(DDZHandCardsType.DDZTypes.FOUR, 6, c.getCardAt(2).value);
		// four with 2 double
		if(s==0 && d==2 && t==0 && f==1)
		{
			if(c.getCardAt(0).value == c.getCardAt(3).value)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.FOUR, 8, c.getCardAt(0).value);
			else if(c.getCardAt(4).value == c.getCardAt(7).value)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.FOUR, 8, c.getCardAt(4).value);
			else
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.FOUR, 8, c.getCardAt(2).value);
		}
		// flush
		if(s>4 && d==0 && t==0 && f==0)
		{
			boolean allowable = true;
			if(c.getCardAt(0).value > 14) // more than Ace is not allowable
				allowable = false;
			for(int i=1; i<c.getLength(); i++) // check from the second one
				if(c.getCardAt(i).value != c.getCardAt(i-1).value-1)
				{
					allowable = false;
					break;
				}
			if(allowable == true)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.FLUSH, c.getLength(), c.getCardAt(0).value);
		}
		// dflush
		if(s==0 && d>2 && t==0 && f==0)
		{
			boolean allowable = true;
			if(c.getCardAt(0).value > 14) // more than Ace is not allowable
				allowable = false;
			for(int i=2; i<c.getLength(); i+=2)
				if(c.getCardAt(i).value != c.getCardAt(i-2).value-1)
				{
					allowable = false;
					break;
				}
			if(allowable == true)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.DFLUSH, c.getLength(), c.getCardAt(0).value);
		}
		// more triples with none
		if(s==0 && d==0 && t>1 && f==0)
		{
			boolean allowable = true;
			if(c.getCardAt(0).value > 14) // more than Ace is not allowable
				allowable = false;
			for(int i=3; i<c.getLength(); i+=3)
				if(c.getCardAt(i).value != c.getCardAt(i-3).value-1)
				{
					allowable = false;
					break;
				}
			if(allowable == true)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, c.getLength(), c.getCardAt(0).value, c.getLength()/3);
		}
		// more triples with single
		if(s==t && d==0 && t>1 && f==0)
		{
			int v=0, l=0;
			boolean allowable = true;
			for(int i=0; i<c.getLength(); i++)
				if(l != c.getCardAt(i).value)
					l = c.getCardAt(i).value;
				else // found the triples' begin, now check if the triples are reasonable or not
				{
					v = c.getCardAt(i).value; // note down the headValue
					for(int j=0; j<t; j++)
					{
						if(c.getCardAt(i-1).value != c.getCardAt(i+2).value+1)
							allowable = false;
						i += 3;
					}
					break;
				}
			if(allowable == true)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, c.getLength(), v, t);
		}
		// more triples with double
		if(s==0 && d==t && t>1 && f==0)
		{
			int v=0, l=0;
			boolean allowable = true;
			for(int i=0; i<c.getLength(); i+=2)
				if(l != c.getCardAt(i).value)
					l = c.getCardAt(i).value;
				else // found the triples' begin, now check if the triples are reasonable or not
				{
					v = c.getCardAt(i).value; // note down the headValue
					for(int j=0; j<t; j++)
					{
						if(c.getCardAt(i-2).value != c.getCardAt(i+1).value+1)
							allowable = false;
						i += 3;
					}
					break;
				}
			if(allowable == true)
				return new DDZHandCardsType(DDZHandCardsType.DDZTypes.TRIPLE, c.getLength(), v, t);
		}
		// do not match any type of above
		return new DDZHandCardsType();
	}
	
	public static int compareBetween(DDZHandCards h1, DDZHandCards h2)
	{
		if(h1.getCardsType().type == DDZHandCardsType.DDZTypes.NONE)
			return -1;
		if(h1.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB 
				&& h2.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB)
			return h1.getCardsType().headValue - h2.getCardsType().headValue;
		if(h1.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB 
				&& h2.getCardsType().type != DDZHandCardsType.DDZTypes.BOMB)
			return 1;
		if(h1.getCardsType().type != DDZHandCardsType.DDZTypes.BOMB 
				&& h2.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB)
			return -1;
		if(h1.getCardsType().type != h2.getCardsType().type)
			return -1;
		else // both have the same type
		{
			if(h1.getCardsType().type == DDZHandCardsType.DDZTypes.TRIPLE)
				if(h1.getCardsType().extra == h2.getCardsType().extra)
					return h1.getCardsType().headValue - h2.getCardsType().headValue;
				else
					return -1;
			else
			{
				if(h1.getCardsType().length == h2.getCardsType().length)
					return h1.getCardsType().headValue - h2.getCardsType().headValue;
				else
					return -1;
			}
		}
	}
	
	/**
	 * Description: find all possible handCards of the same type through a collection of cards.
	 * No duplicate, which means Heart8 is like Club8 and so on. 
	 * Note: as for triple and four, only the main part will be considered, which means, when you
	 * search for a four with singles or doubles, you will get a collection of cards with type BOMB as return.
	 * @param from DDZCards
	 * @param target DDZHandCards
	 * @return a collection of all possible handCards, from big to small
	 */
	public static List<DDZHandCards> findSameType(DDZCards from, DDZHandCards target)
	{
		List<DDZHandCards> results = new ArrayList<DDZHandCards>();
		if(from.getLength()==0)
			return results;
		from.sort();
		// there are 8 types in total
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.NONE)
			return results;
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB)
		{
			if(from.getCardAt(0).getValueEnum() == PokerValueEnum.JOKER 
					&& from.getCardAt(1).getValueEnum() == PokerValueEnum.JOKER)
				results.add(new DDZHandCards(from.getCardsBetween(0, 1)));
			else
			{
				for(int i=0; i<from.getLength()-3; i++)
					if(from.getCardAt(i).value == from.getCardAt(i+3).value)
						results.add(new DDZHandCards(from.getCardsBetween(i, i+3)));
			}
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.FOUR)
		{
			for(int i=0; i<from.getLength()-3; i++)
				if(from.getCardAt(i).value == from.getCardAt(i+3).value)
					results.add(new DDZHandCards(from.getCardsBetween(i, i+3)));
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.DFLUSH)
		{
			int num = target.getCardsType().length/2;
			for(int headValue=14; headValue>1+num; headValue--)
			{
				boolean matchable = true;
				for(int i=0; i<num; i++)
					matchable = matchable && from.findCardByValue(headValue-i)>1;
				if(matchable)
				{
					DDZCards tmp = new DDZCards();
					for(int i=0; i<num; i++)
					{
						tmp.addCard(from.getCardAt(from.findCardByFirstlySee(headValue-i)));
						tmp.addCard(from.getCardAt(from.findCardByFirstlySee(headValue-i)+1));
					}
					results.add(new DDZHandCards(tmp));
				}
			}
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.FLUSH)
		{
			int num = target.getCardsType().length;
			for(int headValue=14; headValue>1+num; headValue--)
			{
				boolean matchable = true;
				for(int i=0; i<num; i++)
					matchable = matchable && from.findCardByValue(headValue-i)>0;
				if(matchable)
				{
					DDZCards tmp = new DDZCards();
					for(int i=0; i<num; i++)
						tmp.addCard(from.getCardAt(from.findCardByFirstlySee(headValue-i)));
					results.add(new DDZHandCards(tmp));
				}
			}
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.DOUBLE)
		{
			for(int headValue=15; headValue>2; headValue--)
			{
				if(from.findCardByValue(headValue)>1)
				{
					DDZCards tmp = new DDZCards();
					tmp.addCard(from.getCardAt(from.findCardByFirstlySee(headValue)));
					tmp.addCard(from.getCardAt(from.findCardByFirstlySee(headValue)+1));
					results.add(new DDZHandCards(tmp));
				}
			}
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.SINGLE)
		{
			int value = 0;
			for(int i=0; i<from.getLength(); i++)
			{
				if(from.getCardAt(i).value != value)
				{
					results.add(new DDZHandCards(from.getCardsBetween(i, i)));
					value = from.getCardAt(i).value;
				}
			}
		}
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.TRIPLE)
		{
			/*
			for(int headValue=15; headValue>2; headValue--)
			{
				if(from.findCardByValue(headValue)>2)
				{
					int i = from.findCardByFirstlySee(headValue);
					results.add(new DDZHandCards(from.getCardsBetween(i, i+2)));
				}
			}
			*/
			int num = target.getCardsType().extra;
			for(int headValue=15; headValue>2; headValue--)
			{
				boolean allowable = true;
				DDZCards tmp = new DDZCards();
				for(int n=0; n<num; n++)
					if(from.findCardByValue(headValue-n)<3)
					{
						allowable = false;
						break;
					}
					else
					{
						int i = from.findCardByFirstlySee(headValue-n);
						tmp.absorb(from.getCardsBetween(i, i+2));
					}
				if(allowable)
					results.add(new DDZHandCards(tmp));
			}
		}
		
		return results;
	}

	/**
	 * Description: given a certain type of cards, find the same type of handCards through a collection of cards.
	 * No duplicate. All types which need additional info, such as flush, dflush, triple, four will not be considered.
	 * @param from
	 * @param type
	 * @return
	 */
	public static List<DDZHandCards> findType(DDZCards from, DDZHandCardsType.DDZTypes type)
	{
		List<DDZHandCards> results = new ArrayList<DDZHandCards>();
		
		if(type == DDZHandCardsType.DDZTypes.SINGLE)
		{
			DDZPokerCard p1 = new DDZPokerCard(PokerSuiteEnum.CLUB, PokerValueEnum.THREE);
			DDZPokerCard[] p = {p1};
			DDZHandCards h = new DDZHandCards(new DDZCards(p));
			
			results = findSameType(from, h);
		}
		if(type == DDZHandCardsType.DDZTypes.DOUBLE)
		{
			DDZPokerCard p1 = new DDZPokerCard(PokerSuiteEnum.CLUB, PokerValueEnum.THREE);
			DDZPokerCard p2 = new DDZPokerCard(PokerSuiteEnum.HEART, PokerValueEnum.THREE);
			DDZPokerCard[] p = {p1, p2};
			DDZHandCards h = new DDZHandCards(new DDZCards(p));
			
			results = findSameType(from, h);
		}
		if(type == DDZHandCardsType.DDZTypes.BOMB)
		{
			DDZPokerCard p1 = new DDZPokerCard(PokerSuiteEnum.BLACK, PokerValueEnum.JOKER);
			DDZPokerCard p2 = new DDZPokerCard(PokerSuiteEnum.RED, PokerValueEnum.JOKER);
			DDZPokerCard[] p = {p1, p2};
			DDZHandCards h = new DDZHandCards(new DDZCards(p));
			
			results = findSameType(from, h);
		}
		
		return results;
	}
	
	public static DDZHandCards findSameTypeBigger(DDZCards from, DDZHandCards target, boolean smallest)
	{
		DDZHandCards result = new DDZHandCards();
		
		if(from.getLength() == 0)
			return result;
		
		List<DDZHandCards> sameType = findSameType(from, target);
		
		// if the target type is NONE, a single handCard will be returned
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.NONE)
		{
			sameType = Rules.findType(from, DDZHandCardsType.DDZTypes.SINGLE);
			
			if(smallest == true)
				result = sameType.get(sameType.size()-1);
			else
				result = sameType.get(0);
		}
		
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.BOMB
			|| target.getCardsType().type == DDZHandCardsType.DDZTypes.DFLUSH
			|| target.getCardsType().type == DDZHandCardsType.DDZTypes.FLUSH
			|| target.getCardsType().type == DDZHandCardsType.DDZTypes.DOUBLE
			|| target.getCardsType().type == DDZHandCardsType.DDZTypes.SINGLE)
		{
			if(sameType.size()!=0 && target.getCardsType().headValue<sameType.get(0).getCardsType().headValue)
				if(smallest == true)
				{
					for(int i=sameType.size()-1; i>-1; i--)
						if(sameType.get(i).getCardsType().headValue > target.getCardsType().headValue)
						{
							result = sameType.get(i);
							break;
						}
				}
				else
					result = sameType.get(0);
		}
		
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.FOUR)
		{
			if(sameType.size() != 0 && target.getCardsType().headValue<sameType.get(0).getCardsType().headValue)
			{
				List<DDZHandCards> tmp; // tmp is used to find the tails
				
				if(target.getCardsType().length == 8) // four with doubles
					tmp = Rules.findType(from, DDZHandCardsType.DDZTypes.DOUBLE);
				else // four with singles			
					tmp = Rules.findType(from, DDZHandCardsType.DDZTypes.SINGLE);
				
				if(tmp.size() > 2) // the tmp should contain four itself, so the tmp size should be at least 3
				{
					if(smallest == true)
					{
						DDZCards tmpResult = new DDZCards();
						int v=0;
						for(int i=sameType.size()-1; i>-1; i--)
							if(sameType.get(i).getCardsType().headValue > target.getCardsType().headValue)
							{
								v = sameType.get(i).getCardsType().headValue;
								tmpResult.absorb(sameType.get(i).getDDZCards());
								break;
							}

						int found = 0;
						for(int i=tmp.size()-1; i>-1 && found<2; i--)
							if(tmp.get(i).getCardsType().headValue != v)
							{	
								tmpResult.absorb(tmp.get(i).getDDZCards());
								found++;
							}
						result = new DDZHandCards(tmpResult);
					}
					else
					{
						DDZCards tmpResult = sameType.get(0).getDDZCards();
						int v = sameType.get(0).getCardsType().headValue;
						int found = 0;
						for(int i=0; i<tmp.size() && found<2; i++)
							if(tmp.get(i).getCardsType().headValue != v)
							{	
								tmpResult.absorb(tmp.get(i).getDDZCards());
								found++;
							}
						result = new DDZHandCards(tmpResult);
					}
				}
			}
		}
		
		if(target.getCardsType().type == DDZHandCardsType.DDZTypes.TRIPLE)
		{
			if(sameType.size() != 0)
			{
				List<DDZHandCards> tmp; // tmp is used to find the tails
				
				if((target.getCardsType().length/target.getCardsType().extra-3) == 2) // triple with doubles		
					tmp = Rules.findType(from, DDZHandCardsType.DDZTypes.DOUBLE);
				else // triple with singles
					tmp = Rules.findType(from, DDZHandCardsType.DDZTypes.SINGLE);
				
				if(tmp.size() > target.getCardsType().extra) // same reason above
				{
					if(smallest == true)
					{
						DDZCards tmpResult = new DDZCards();
						int v=0;
						for(int i=sameType.size()-1; i>-1; i--)
							if(sameType.get(i).getCardsType().headValue > target.getCardsType().headValue)
							{
								v = sameType.get(i).getCardsType().headValue;
								tmpResult.absorb(sameType.get(i).getDDZCards());
								break;
							}
						
						int found = 0;
						for(int i=tmp.size()-1; i>-1 && found<target.getCardsType().extra; i--)
							if(tmp.get(i).getCardsType().headValue != v)
							{	
								tmpResult.absorb(tmp.get(i).getDDZCards());
								found++;
							}
						result = new DDZHandCards(tmpResult);
					}
					else
					{
						DDZCards tmpResult = sameType.get(0).getDDZCards();
						int v = sameType.get(0).getCardsType().headValue;
						int found = 0;
						for(int i=0; i<tmp.size() && found<target.getCardsType().extra; i++)
							if(tmp.get(i).getCardsType().headValue != v)
							{	
								tmpResult.absorb(tmp.get(i).getDDZCards());
								found++;
							}
						result = new DDZHandCards(tmpResult);
					}
				}
			}
		}
		
		return result;
	}
	
	public static DDZHandCards findBigger(DDZCards from, DDZHandCards target, boolean smallest)
	{
		DDZHandCards result = Rules.findSameTypeBigger(from, target, smallest);
		
		// take BOMB into consideration
		List<DDZHandCards> bomb = Rules.findType(from, DDZHandCardsType.DDZTypes.BOMB);
		if(smallest == true && result.getDDZCards().getLength() == 0)		
			if(bomb.size() != 0)
				result = bomb.get(bomb.size()-1);
		if(smallest == false)
			if(bomb.size() != 0)
				result = bomb.get(0);
		
		return result;
	}
}